Data API Reference
The Data API reads, writes, and aggregates records stored in Corva datasets and discovers the dataset definitions available to the authenticated customer.
Base URL: https://data.corva.ai
All examples accept either an API key or a Bearer token:
Authorization: API YOUR_API_KEY
Authorization: Bearer YOUR_JWT
The required parameters, validation limits, and examples below are verified against the current Data API routes and models. The interactive API explorer is useful for trying requests, but this page is the customer reference for the workflows shown here.
Dataset record shape
Dataset records share a common envelope. Fields in data are specific to the selected dataset.
| Field | Type | Description |
|---|---|---|
_id | string | MongoDB document ID. |
company_id | integer | Owning company ID, when present. |
asset_id | integer | Platform asset ID, commonly a well's asset_id. |
version | integer | Record schema/version value. |
provider | string | Dataset provider, such as corva. |
collection | string | Dataset collection name, such as wits. |
data | object | Dataset-specific payload. |
timestamp | integer | Unix timestamp in milliseconds for time-indexed records, when present. |
measured_depth | number | Measured depth for depth-indexed records, when present. |
log_identifier | string | App-stream/log scope for depth data, when present. |
stage_number | integer | Completion stage number, when present. |
datetime | string | ISO 8601 time for time-series collections, when present. |
metadata | object | Time-series metadata, including company_id or asset_id, when present. |
Representative response:
[
{
"_id": "507f1f77bcf86cd799439011",
"company_id": 100,
"asset_id": 12345,
"version": 1,
"provider": "corva",
"collection": "wits",
"timestamp": 1715600000000,
"data": {
"hole_depth": 10482.5,
"bit_depth": 10476.2
}
}
]
Insert dataset records
/api/v1/data/{provider}/{dataset}/Insert records by sending a JSON array. A single request must contain at least 1 and no more than 1,000 records. Split larger writes into batches of 1,000 or fewer records.
The caller needs write permission for the dataset and access to the referenced company and asset.
| Body field | Type | Required | Description |
|---|---|---|---|
version | integer | Yes | Record schema/version value. |
asset_id | integer | For time/depth data | Asset ID associated with the record. |
timestamp | integer | For time data | Unix timestamp for a time-indexed record. |
measured_depth | number | For depth data | Measured depth for a depth-indexed record. |
log_identifier | string | For depth data | App-stream/log scope. |
data | object | Dataset dependent | Dataset-specific record payload. Follow the selected dataset's schema. |
curl 'https://data.corva.ai/api/v1/data/example-provider/example-dataset/' \
--request POST \
--header "Authorization: API ${CORVA_API_KEY}" \
--header 'Content-Type: application/json' \
--data '[
{
"version": 1,
"asset_id": 12345,
"timestamp": 1715600000000,
"data": {"value": 42.1}
},
{
"version": 1,
"asset_id": 12345,
"timestamp": 1715600001000,
"data": {"value": 42.4}
}
]'
records = [
{
"version": 1,
"asset_id": 12345,
"timestamp": 1715600000000,
"data": {"value": 42.1},
},
{
"version": 1,
"asset_id": 12345,
"timestamp": 1715600001000,
"data": {"value": 42.4},
},
]
response = requests.post(
"https://data.corva.ai/api/v1/data/example-provider/example-dataset/",
headers={"Authorization": authorization},
json=records,
timeout=30,
)
response.raise_for_status()
result = response.json()
A successful response reports inserted IDs and any failed records:
{
"inserted_ids": [
"507f1f77bcf86cd799439011",
"507f1f77bcf86cd799439012"
],
"failed_count": 0,
"messages": []
}
Do not resend an entire batch blindly after a partial failure. Check inserted_ids, failed_count, and messages, then retry only records that were not inserted.
Find dataset records
/api/v1/data/{provider}/{dataset}/Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | Dataset provider, such as corva. |
dataset | string | Yes | Dataset collection name, such as wits. |
Query parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query | JSON object encoded as a string | Yes | — | MongoDB-style match conditions. |
sort | JSON object encoded as a string | Yes | — | Sort fields using 1 for ascending and -1 for descending. |
limit | integer | Yes | — | Maximum records to return; 1 through 10000. |
skip | integer | No | 0 | Records to skip; must be 0 or greater. |
fields | comma-separated string | No | all fields | Fields to return, such as asset_id,timestamp,data.hole_depth. |
include_count | boolean | No | false | When true, returns the total matching count in the Total response header. |
query and sort must be valid JSON objects, not JavaScript or Python object syntax.
Example: latest records for one well
curl --get 'https://data.corva.ai/api/v1/data/corva/wits/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'query={"asset_id":12345}' \
--data-urlencode 'sort={"timestamp":-1}' \
--data-urlencode 'limit=100' \
--data-urlencode 'fields=asset_id,timestamp,data.hole_depth,data.bit_depth'
import json
import os
import requests
response = requests.get(
"https://data.corva.ai/api/v1/data/corva/wits/",
headers={"Authorization": f"API {os.environ['CORVA_API_KEY']}"},
params={
"query": json.dumps({"asset_id": 12345}),
"sort": json.dumps({"timestamp": -1}),
"limit": 100,
"fields": "asset_id,timestamp,data.hole_depth,data.bit_depth",
},
timeout=30,
)
response.raise_for_status()
records = response.json()
Common query conditions
| Goal | query value before URL encoding |
|---|---|
| One asset | {"asset_id":12345} |
| Several assets | {"asset_id":{"$in":[12345,67890]}} |
| Time range | {"asset_id":12345,"timestamp":{"$gte":1715600000000,"$lt":1715686400000}} |
| Depth range | {"asset_id":12345,"measured_depth":{"$gte":10000,"$lt":11000}} |
| Completion stage | {"asset_id":12345,"stage_number":12} |
| Nested data field | {"asset_id":12345,"data.status":"active"} |
Count records
/api/v1/data/{provider}/{dataset}/count/| Parameter | Type | Required | Description |
|---|---|---|---|
query | JSON object encoded as a string | Yes | Match conditions. |
curl --get 'https://data.corva.ai/api/v1/data/corva/wits/count/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'query={"asset_id":12345}'
{
"count": 42810
}
Use this endpoint when only the count is needed. For records plus a count, use include_count=true on the find endpoint and read the Total response header.
Find records with a JSON request body
/api/v1/data/{provider}/{dataset}/list/This endpoint performs the same read operation as the GET endpoint but keeps large or complex query values out of the URL.
| Body field | Type | Required | Default | Description |
|---|---|---|---|---|
query | object | Yes | — | Match conditions. |
sort | object | Yes | — | Sort conditions. |
limit | integer | Yes | — | 1 through 10000. |
skip | integer | No | 0 | Records to skip. |
fields | string | No | all fields | Comma-separated fields. |
include_count | boolean | No | false | Add the total to the Total response header. |
curl 'https://data.corva.ai/api/v1/data/corva/wits/list/' \
--request POST \
--header "Authorization: API ${CORVA_API_KEY}" \
--header 'Content-Type: application/json' \
--data '{
"query": {
"asset_id": 12345,
"timestamp": {"$gte": 1715600000000, "$lt": 1715686400000}
},
"sort": {"timestamp": 1},
"skip": 0,
"limit": 1000,
"fields": "asset_id,timestamp,data.hole_depth",
"include_count": true
}'
Simple aggregation
/api/v1/data/{provider}/{dataset}/aggregate/| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
match | JSON object encoded as a string | Yes | — | $match expression. |
sort | JSON object encoded as a string | Yes | — | Sort expression. |
limit | integer | Yes | — | 1 through 10000. Applied before grouping. |
skip | integer | No | 0 | Records to skip before grouping. |
group | JSON object encoded as a string | No | — | $group expression. |
project | JSON object encoded as a string | No | — | $project expression. |
curl --get 'https://data.corva.ai/api/v1/data/corva/wits/aggregate/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'match={"asset_id":12345}' \
--data-urlencode 'sort={"timestamp":-1}' \
--data-urlencode 'limit=10000' \
--data-urlencode 'group={"_id":"$asset_id","max_hole_depth":{"$max":"$data.hole_depth"}}' \
--data-urlencode 'project={"_id":0,"asset_id":"$_id","max_hole_depth":1}'
[
{
"asset_id": 12345,
"max_hole_depth": 10482.5
}
]
Aggregation pipeline
Use a pipeline for multiple aggregation stages. Prefer POST when the pipeline is non-trivial.
/api/v1/data/{provider}/{dataset}/aggregate/pipeline/The body must contain a non-empty stages array.
curl 'https://data.corva.ai/api/v1/data/corva/wits/aggregate/pipeline/' \
--request POST \
--header "Authorization: API ${CORVA_API_KEY}" \
--header 'Content-Type: application/json' \
--data '{
"stages": [
{"$match": {"asset_id": 12345}},
{"$sort": {"timestamp": -1}},
{"$limit": 1000},
{"$group": {
"_id": "$asset_id",
"max_hole_depth": {"$max": "$data.hole_depth"}
}},
{"$project": {
"_id": 0,
"asset_id": "$_id",
"max_hole_depth": 1
}}
]
}'
The GET form uses the same path and accepts the entire array in a required stages query parameter:
GET /api/v1/data/{provider}/{dataset}/aggregate/pipeline/?stages=[...]
Only allowed aggregation stages can run. Pipelines must be non-empty, and write-oriented stages such as $merge are not available to ordinary customer read requests.
Discover datasets
Search accessible definitions
/api/v1/dataset/| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | No | Search by dataset name. Defaults to an empty search. |
plottable | boolean | No | Filter by whether the dataset can be plotted. |
temporary | boolean | No | Filter temporary datasets. |
curl --get 'https://data.corva.ai/api/v1/dataset/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'search=wits' \
--data-urlencode 'temporary=false'
Representative definition:
[
{
"id": 15,
"company_id": 100,
"provider": "corva",
"name": "corva#wits",
"friendly_name": "wits",
"description": "WITS data",
"schema": {},
"data_type": "time",
"plottable": true,
"temporary": false,
"permission_workflow": {
"read": "request",
"write": "request",
"delete": "request"
},
"indexes": [],
"statistics": {
"size": 1000000,
"count": 282034,
"storage_size": 8839043,
"index_size": 199234282
}
}
]
List datasets by company
/api/v1/dataset/company/| Parameter | Type | Required | Description |
|---|---|---|---|
plottable | boolean | No | Filter by whether the dataset can be plotted. |
type | string | No | Dataset type: time, depth, reference, or timeseries. |
curl --get 'https://data.corva.ai/api/v1/dataset/company/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'type=time'
The response groups available datasets by company name:
{
"Example Company": [
{
"id": 44,
"provider": "corva",
"name": "activities",
"type": "time"
}
]
}
Get one definition
/api/v1/dataset/{provider}/{name}/curl 'https://data.corva.ai/api/v1/dataset/corva/wits/' \
--header "Authorization: API ${CORVA_API_KEY}"
Paging safely
For record reads, increment skip by the number of records returned and keep the same deterministic sort. Stop when the response contains fewer records than limit. If records can be added during a long export, prefer cursor-like time/depth windows to a changing global offset.
skip = 0
limit = 10000
while True:
response = requests.get(
"https://data.corva.ai/api/v1/data/corva/wits/",
headers={"Authorization": authorization},
params={
"query": json.dumps({"asset_id": 12345}),
"sort": json.dumps({"timestamp": 1, "_id": 1}),
"skip": skip,
"limit": limit,
},
timeout=30,
)
response.raise_for_status()
batch = response.json()
if not batch:
break
process(batch)
skip += len(batch)
if len(batch) < limit:
break
Errors
| Status | Common cause |
|---|---|
401 Unauthorized | Invalid API key/Bearer token or malformed Authorization header. |
403 Forbidden | The identity lacks dataset, company, asset, or well permissions. |
404 Not Found | Provider or dataset does not exist or is not visible to the identity. |
422 Unprocessable Content | Missing required fields; invalid JSON or sort direction; a value outside its range; or an insert body containing fewer than 1 or more than 1,000 records. |
For task-oriented explanations, see Read Dataset Records and Aggregate Dataset Data. The interactive Data API explorer remains available for endpoints not yet covered here.